﻿using System;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Xml.Serialization;

using SharpGL.SceneGraph.Effects;

namespace SharpGL.SceneGraph
{
    /// <summary>
    /// The base class for all elements in a scene. Anything in
    /// the scene tree is a Scene Element. Scene elements can
    /// have children but are very lacking in functionality -
    /// implement.
    /// </summary>
    [XmlInclude(typeof(Primitives.Axies))]
    [XmlInclude(typeof(Primitives.Cube))]
    [XmlInclude(typeof(Primitives.Folder))]
    [XmlInclude(typeof(Primitives.Grid))]
    [XmlInclude(typeof(Primitives.Polygon))]
    [XmlInclude(typeof(Primitives.Polyline))]
    [XmlInclude(typeof(Primitives.SmartAxies))]
    [XmlInclude(typeof(Primitives.SolidPolyline))]
    [XmlInclude(typeof(Primitives.TexturedQuad))]
    [XmlInclude(typeof(Quadrics.Quadric))]
    [XmlInclude(typeof(Quadrics.Cylinder))]
    [XmlInclude(typeof(Quadrics.Disk))]
    [XmlInclude(typeof(Quadrics.Sphere))]
    [XmlInclude(typeof(Lighting.Light))]
    [XmlInclude(typeof(Lighting.Spotlight))]
    [XmlInclude(typeof(Effects.LinearTransformationEffect))]
    [XmlInclude(typeof(Effects.OpenGLAttributesEffect))]
    [XmlInclude(typeof(Text.Text2DObject))]
    [XmlInclude(typeof(Text.Text3DObject))]
    public abstract partial class SceneElement1
    {
        #region Private Fields

        /// <summary>
        /// The parent scene element.
        /// </summary>
        private SceneElement1 parent = null;

        /// <summary>
        /// The name.
        /// </summary>
        private string name = "Scene element";

        /// <summary>
        /// a value indicating whether this instance is enabled.
        /// </summary>
        private bool isEnable = true;

        /// <summary>
        /// The children of the element.
        /// </summary>
        private ObservableCollection<SceneElement1> children = new ObservableCollection<SceneElement1>();

        /// <summary>
        /// The effects.
        /// </summary>
        private ObservableCollection<Effect> effects = new ObservableCollection<Effect>();

        #endregion Private Fields

        #region Private Methods

        private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (SceneElement1 sceneElement in e.NewItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.Add, sceneElement));
                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (SceneElement1 sceneElement in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.Remove, sceneElement));
                    break;

                case NotifyCollectionChangedAction.Replace:
                    foreach (SceneElement1 sceneElement in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.Remove, sceneElement));
                    foreach (SceneElement1 sceneElement in e.NewItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.Add, sceneElement));
                    break;

                case NotifyCollectionChangedAction.Reset:
                    foreach (SceneElement1 sceneElement in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.Remove, sceneElement));
                    break;
            }
        }

        private void OnEffectsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (Effect effect in e.NewItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectAdd, effect));
                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (Effect effect in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectRemove, effect));
                    break;

                case NotifyCollectionChangedAction.Replace:
                    foreach (Effect effect in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectRemove, effect));
                    foreach (Effect effect in e.NewItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectAdd, effect));
                    break;

                case NotifyCollectionChangedAction.Reset:
                    foreach (Effect effect in e.OldItems)
                        DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectRemove, effect));
                    break;
            }
        }

        /// <summary>
        /// Called when scene tree changed.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A SceneTreeChangeEventArgs that contains the event data.</param>
        private void OnSceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            DoSceneTreeChanged(sender, e);
        }

        private void OnEffectChanged(object sender, EffectChangeEventArgs e)
        {
            switch (e.Action)
            {
                case EffectChangeAction.PropertyChange:
                    DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectPropertyChange, (Effect)sender));
                    break;

                case EffectChangeAction.CreateInContext:
                    DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectCreateInContext, (Effect)sender));
                    break;

                case EffectChangeAction.DestroyInContext:
                    DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.EffectDestroyInContext, (Effect)sender));
                    break;
            }
        }
        
        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Call this function to do the SceneTreeChanged event.
        /// </summary>
        protected void DoSceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            var handler = SceneTreeChanged;
            if (handler != null)
                handler(sender, e);
        }

        #endregion Protected Methods

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SceneElement"/> class.
        /// </summary>
        public SceneElement1()
        {
            children.CollectionChanged += new NotifyCollectionChangedEventHandler(OnChildrenCollectionChanged);
            effects.CollectionChanged += new NotifyCollectionChangedEventHandler(OnEffectsCollectionChanged);
        }

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// Adds a child.
        /// </summary>
        /// <param name="child">The child scene element.</param>
        public virtual void AddChild(SceneElement1 child)
        {
            if (child != null)
            {
                //  Throw an exception if the child already has a parent.
                if (child.Parent != null)
                    throw new Exception("Can't add the child element '" +
                                        child.Name +
                                        "' - it is already in the Scene Tree.");

                //  Add the child and set the parent.
                child.SceneTreeChanged += new SceneTreeChangeEventHandler(OnSceneTreeChanged);
                child.Parent = this;
                Children.Add(child);
            }
        }

        /// <summary>
        /// Removes the child scene element.
        /// </summary>
        /// <param name="child">The child scene element.</param>
        public void RemoveChild(SceneElement1 child)
        {
            if (child != null)
            {
                //  Throw an exception if the child is not a child of this element..
                if (child.Parent != this)
                    throw new Exception("Can't remove the child element '"
                                        + child.Name +
                                        "' - it is not a child of '" +
                                        Name + "'.");

                //  Remove the child.
                Children.Remove(child);
                child.Parent = null;
                child.SceneTreeChanged -= new SceneTreeChangeEventHandler(OnSceneTreeChanged);
            }
        }

        /// <summary>
        /// Adds an effect.
        /// </summary>
        /// <param name="effect">The effect.</param>
        public void AddEffect(Effect effect)
        {
            if (effect != null)
            {
                effect.EffectChanged += new EffectChangeEventHandler(OnEffectChanged);
                Effects.Add(effect);
            }
        }

        /// <summary>
        /// Removes the effect.
        /// </summary>
        /// <param name="effect">The effect.</param>
        public void RemoveEffect(Effect effect)
        {
            if (effect != null)
            {
                Effects.Remove(effect);
                effect.EffectChanged -= new EffectChangeEventHandler(OnEffectChanged);
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// a <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            //  Return the name.
            return GetType().Name + ": " + Name;
        }

        #endregion

        #region Public Events

        [Description("Called when scene tree has been changed."), Category("SharpGL")]
        public event SceneTreeChangeEventHandler SceneTreeChanged;

        #endregion Public Events
        
        #region Properties

        /// <summary>
        /// Gets the parent.
        /// </summary>
        [Description("The parent scene element."), Category("Scene Element")]
        public virtual SceneElement1 Parent
        {
            get { return parent; }
            protected set { parent = value; }
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        [Description("The object name."), Category("Scene Element")]
        public virtual string Name
        {
            get { return name; }
            set
            {
                name = value;

                DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.PropertyChange, this.GetType().GetProperty("Name")));
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is enabled; otherwise, <c>false</c>.
        /// </value>
        [Description("If true, the object participates in rendering."), Category("Scene Element")]
        public virtual bool IsEnabled
        {
            get { return isEnable; }
            set
            {
                isEnable = value;

                DoSceneTreeChanged(this, new SceneTreeChangeEventArgs(SceneTreeChangeAction.PropertyChange, this.GetType().GetProperty("IsEnabled")));
            }
        }

        /// <summary>
        /// Gets the children.
        /// </summary>
        [Browsable(false)]
        public ObservableCollection<SceneElement1> Children
        {
            get { return children; }
        }

        /// <summary>
        /// Gets the effects.
        /// </summary>
        [Browsable(false)]
        public ObservableCollection<Effect> Effects
        {
            get { return effects; }
        }

        /// <summary>
        /// Gets the joined bounding volumes of all child elements.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public BoundingVolume JoinedChildrenBoundingVolume
        {
            get
            {
                BoundingVolume boundingVolume = null;
                IVolumeBound boundedElement;

                foreach (var childElement in Traverse())
                {
                    if (childElement is IVolumeBound)
                    {
                        boundedElement = childElement as IVolumeBound;
                        if (null == boundingVolume)
                            boundingVolume = boundedElement.BoundingVolume;
                        else
                            boundingVolume.Union(boundedElement.BoundingVolume);
                    }
                }

                return (boundingVolume == null ? new BoundingVolume() : boundingVolume);
            }
        }

        #endregion
    }
}